package de.Niklas.Laser.Listener;

import de.Niklas.Laser.LaserPlugin;
import de.Niklas.Laser.util.ConfigUtil.Bundle;
import de.Niklas.Laser.util.ParticleEffect;
import de.Niklas.Laser.util.breakEvent.LaserBreakBlockEvent;
import de.Niklas.Laser.util.breakEvent.LaserDamageEvent;

import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;

public class LaserListener implements Listener {
    LaserPlugin plugin;
    ArrayList<UUID> targets;
    ArrayList<String> cooldown;
    BlockFace[] minimalValues;
    public static Object DUST_COLOR;
    static {
        try {
            Class<?> DUST_OPTIONS_CLASS = Class.forName("org.bukkit.Particle$DustOptions");
            Constructor<?> DUST_OPTIONS_METHOD = DUST_OPTIONS_CLASS.getConstructor(Color.class, float.class);
            DUST_COLOR = DUST_OPTIONS_METHOD.newInstance(Color.RED, (float)1);
        } catch (ReflectiveOperationException ignored) {}
    }

    public LaserListener(final LaserPlugin plugin) {
        this.targets = new ArrayList<>();
        this.cooldown = new ArrayList<>();
        this.minimalValues = new BlockFace[]{BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST};
        this.plugin = plugin;
    }

    @EventHandler
    public void onLaserInteract(final PlayerInteractEvent e) {
        if (Bundle.OPTIONS_INVERTSHOOTING.value) {
            if (e.getAction() != Action.LEFT_CLICK_AIR && e.getAction() != Action.LEFT_CLICK_BLOCK) {
                return;
            }
        } else if (e.getAction() != Action.RIGHT_CLICK_AIR && e.getAction() != Action.RIGHT_CLICK_BLOCK) {
            return;
        }
        final Player p = e.getPlayer();
        if (!this.istLaser(e.getItem())) {
            return;
        }
        if (!p.hasPermission("laser.use")) {
            return;
        }
        e.setCancelled(true);
        if (Bundle.OPTIONS_COOLDOWN.value != 0) {
            if (this.cooldown.contains(p.getName())) {
                p.sendMessage(this.plugin.getLangString(LaserPlugin.Message.laserCooling));
                return;
            }
            this.cooldown.add(p.getName());
            this.plugin.getServer().getScheduler().scheduleSyncDelayedTask(this.plugin, () ->
                    LaserListener.this.cooldown.remove(p.getName()), Bundle.OPTIONS_COOLDOWN.value
            );
        }
        if (Bundle.OPTIONS_AMMONEEDED.value && !this.entferneMun(p)) {
            p.sendMessage(this.plugin.getLangString(LaserPlugin.Message.laserAmmoNeeded));
            return;
        }
        this.ncpExempt(p, 2);
        if (Bundle.OPTIONS_KNOCKBACK.value && !this.plugin.imScope(p)) {
            p.setVelocity(p.getLocation().getDirection().multiply(-1).setY(0.1));
        }
        final Location playerloc = p.getLocation().add(0.0, 1.1, 0.0);
        if (this.plugin.imScope(p)) {
            playerloc.add(0.0, 0.5, 0.0);
        }
        final Location blockloc = playerloc.clone().add(playerloc.getDirection().multiply(100));
        this.laserbeam(playerloc, blockloc, Bundle.OPTIONS_LENGTH.value * 10, p);
        try {
            p.getWorld().playSound(playerloc, Sound.valueOf((String) Bundle.OPTIONS_SOUND_NAME.value), (float) Bundle.OPTIONS_SOUND_VOLUME.value, (float) Bundle.OPTIONS_SOUND_PITCH.value);
        } catch (Exception ex) {
            p.getWorld().playSound(playerloc, Sound.ENTITY_CAT_HISS, 0.8f, 2.0f);
        }
    }

    public void laserbeam(final Location start, final Location end, final int repeatCount, final Player p) {
        final double dist = Math.abs(end.distance(start));
        final List<LivingEntity> entities = start.getWorld().getLivingEntities();
        for (int i = -1; i < repeatCount; ++i) {
            final double delta = i / 10.0 / dist;
            final double x = (1.0 - delta) * start.getX() + delta * (end.getX() + 0.5);
            final double y = (1.0 - delta) * start.getY() + delta * (end.getY() + 0.5);
            final double z = (1.0 - delta) * start.getZ() + delta * (end.getZ() + 0.5);
            final Location l = new Location(start.getWorld(), x, y, z);
            try {
                ParticleEffect.REDSTONE.display(new ParticleEffect.OrdinaryColor(Bundle.OPTIONS_COLOR_RED.value, Bundle.OPTIONS_COLOR_GREEN.value, Bundle.OPTIONS_COLOR_BLUE.value), l, 257.0);
            } catch (Exception ex) {
                //l.getWorld().playEffect(l, Effect.COLOURED_DUST, 0);
                l.getWorld().spawnParticle(Particle.REDSTONE, l, 5, DUST_COLOR);
            }
            if (Bundle.OPTIONS_DESTROYBLOCKS.value && (p == null || p.hasPermission("laser.use.blocks"))) {
                final Block block = start.getWorld().getBlockAt(l);
                if (((List) Bundle.OPTIONS_MATERIALS.value).contains(block.getType().name())) {
                    Bukkit.getPluginManager().callEvent(new BlockBreakEvent(block, p));
                }
            }
            if (Bundle.OPTIONS_STOPWHENHITTINGBLOCK.value && i > 5) {
                final Block block = start.getWorld().getBlockAt(l);
                if (block.getType().isSolid()) {
                    if (block.getType() == Material.GLASS && Bundle.OPTIONS_REFLECTWHENHITTINGGLASS.value) {
                        final Vector oldV = l.clone().toVector().subtract(start.clone().toVector());
                        final Vector lotV = this.getLot(l);
                        final double val = 2.0 * oldV.clone().dot(lotV.clone());
                        final Vector newV = oldV.clone().subtract(lotV.clone().multiply(val));
                        this.laserbeam(l, l.clone().add(newV), repeatCount - i, p);
                        break;
                    }
                    if (Bundle.OPTIONS_EXPLOSIONWHENHITTINGBLOCK.value) {
                        l.getWorld().createExplosion(l.getX(), l.getY(), l.getZ(), 1.0f, false, Bundle.OPTIONS_DESTROYBLOCKS.value);
                        break;
                    }
                    break;
                }
            }
            if (Bundle.OPTIONS_DAMAGEENABLED.value) {
                for (final LivingEntity entity : entities) {
                    if (entity == p) {
                        continue;
                    }
                    if (l.distance(entity.getEyeLocation()) >= 2.0) {
                        continue;
                    }
                    if (entity instanceof Player && !Bundle.OPTIONS_DAMAGEPLAYERS.value) {
                        continue;
                    }
                    if (this.targets.contains(entity.getUniqueId())) {
                        continue;
                    }
                    this.targets.add(entity.getUniqueId());
                    entity.damage(Bundle.OPTIONS_DAMAGE.value, p);
                    this.removeTarget(entity);
                }
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onBreakBlock(final BlockBreakEvent e) {
        if (!e.getPlayer().getInventory().getItemInMainHand().isSimilar(this.plugin.bekommeLaser())) {
            return;
        }
        if (e.isCancelled()) {
            return;
        }
        this.plugin.getServer().getPluginManager().callEvent(new LaserBreakBlockEvent(e.getBlock(), e.getPlayer()));
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onDamagePlayer(final EntityDamageByEntityEvent e) {
        if (!(e.getDamager() instanceof Player)) {
            return;
        }
        final Player p = (Player) e.getDamager();
        if (!p.getInventory().getItemInMainHand().isSimilar(this.plugin.bekommeLaser())) {
            return;
        }
        if (e.isCancelled()) {
            return;
        }
        this.plugin.getServer().getPluginManager().callEvent(new LaserDamageEvent(p, e.getEntity()));
    }

    @EventHandler
    public void onLaserBreak(final LaserBreakBlockEvent e) {
        final Block b = e.getBlock();
        ParticleEffect.BLOCK_CRACK.display(new ParticleEffect.BlockData(b.getType(), b.getData()), 0.0f, 0.0f, 0.0f, 0.0f, 50, b.getLocation().add(0.5, 0.5, 0.5), 30.0);
        b.breakNaturally(new ItemStack(Material.DIAMOND_PICKAXE));
    }

    @EventHandler
    public void onLaserDamage(final LaserDamageEvent e) {
        ParticleEffect.VILLAGER_ANGRY.display(0.3f, 0.3f, 0.3f, 0.0f, 4, e.getDamaged().getLocation(), 257.0);
    }

    public Vector getLot(final Location l) {
        BlockFace smallestBF = BlockFace.UP;
        double smallestDist = 1000.0;
        BlockFace[] minimalValues;
        for (int length = (minimalValues = this.minimalValues).length, i = 0; i < length; ++i) {
            final BlockFace bf = minimalValues[i];
            final Block b = l.getBlock().getRelative(bf);
            final Location tempMidBlock = this.getMidLocation(b.getLocation());
            if (l.distanceSquared(tempMidBlock) < smallestDist) {
                smallestDist = l.distanceSquared(tempMidBlock);
                smallestBF = bf;
            }
        }
        if (smallestBF == BlockFace.UP) {
            return new Vector(0, 1, 0);
        }
        if (smallestBF == BlockFace.DOWN) {
            return new Vector(0, 1, 0);
        }
        if (smallestBF == BlockFace.NORTH) {
            return new Vector(0, 0, 1);
        }
        if (smallestBF == BlockFace.EAST) {
            return new Vector(1, 0, 0);
        }
        if (smallestBF == BlockFace.SOUTH) {
            return new Vector(0, 0, 1);
        }
        return new Vector(1, 0, 0);
    }

    public Location getMidLocation(final Location l) {
        return new Location(l.getWorld(), l.getBlockX() + 0.5, l.getBlockY() + 0.5, l.getBlockZ() + 0.5);
    }

    public boolean istLaser(final ItemStack is) {
        try {
            return is.getItemMeta().getDisplayName().equalsIgnoreCase(LaserPlugin.STRING_LASER);
        } catch (Exception e) {
            return false;
        }
    }

    public void removeTarget(final LivingEntity e) {
        this.plugin.getServer().getScheduler().scheduleSyncDelayedTask(this.plugin, new Runnable() {
            @Override
            public void run() {
                LaserListener.this.targets.remove(e.getUniqueId());
            }
        }, 2L);
    }

    public boolean entferneMun(final Player p) {
        int counter = 0;
        final ItemStack[] ammoliste = new ItemStack[36];
        for (final ItemStack item : p.getInventory()) {
            if (this.plugin.bekommeMun().isSimilar(item)) {
                ammoliste[counter] = item;
                ++counter;
            }
        }
        if (ammoliste[0] == null) {
            return false;
        }
        if (ammoliste[0].getAmount() == 1) {
            p.getInventory().remove(ammoliste[0]);
            p.updateInventory();
            return true;
        }
        ammoliste[0].setAmount(ammoliste[0].getAmount() - 1);
        p.updateInventory();
        return true;
    }

    public void ncpExempt(final Player p, final int ticks) {
        if (!LaserPlugin.hasNCP) {
            return;
        }
        if (NCPExemptionManager.isExempted(p, CheckType.ALL)) {
            return;
        }
        NCPExemptionManager.exemptPermanently(p, CheckType.ALL);
        Bukkit.getScheduler().runTaskLater(this.plugin, new Runnable() {
            @Override
            public void run() {
                NCPExemptionManager.unexempt(p);
            }
        }, ticks);
    }
}
